package com.rengwuxian.coursecoroutines._4_flow

import com.rengwuxian.coursecoroutines.common.Contributor
import com.rengwuxian.coursecoroutines.common.gitHub
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.produce
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.EmptyCoroutineContext

/**
 * Channel的工作模式
 * Channel、SendChannel/send()、ReceiveChannel/receive()
 *
 * produce 大括号里的ProducerScope是继承自SendChannel的，可以发送数据。
 *         它的返回值是ReceiveChannel，可以拿它来接收数据。
 *         其实这两个是一个对象，类似与前面launch的分析。奥妙就在ProducerCoroutine中（源码）
 *
 * Channel的另一种用法，使用工厂函数。根据参数不同，创建不同的Channel
 * val channel = Channel<List<Contributor>>()
 * 一个协程中发送数据：
 * scope.launch {
 *     channel.send(gitHub.contributors("square", "retrofit"))
 * }
 * 另一个协程中接收数据：
 * scope.launch {
 *     while (isActive) {
 *       channel.receive()
 *     }
 * } // for循环用在channel上是类似效果
 * 上面是Channel最纯净的用法。
 *
 * 协程的Channel指的就是一种在协程之间共享数据的通道，这个通道是单向的，有入口有出口。可以在任何协程里在入口塞数据，可以在任何协程里在出口取东西。
 *
 * produce是对上面写法的一种捆绑和封装，接口委托给外部传进去的Channel。Channel的创建和发送放在一起。
 *
 * SendChannel和ReceiveChannel是出于接口的封装，给开发者提供尽量简单、干净的API。
 * Channel是同时实现了SendChannel和ReceiveChannel的。
 *
 * Channel类似于BlockingQueue，是协程版的BlockingQueue，把阻塞式的实现变成了挂起式。
 * BlockingQueue：阻塞式队列，当队列里的元素满了之后，如果再尝试往队列里加入新元素，就会卡住线程，直到有元素被移出、腾出了位置的时候，这个元素才会被塞进队列。
 *               以及读数据的时候，如果队列已经空了，那就让线程卡住，直到有了新元素插入，再把刚插入的新元素读出来。
 * Channel的实现方式，让它不适合做可订阅的事件流，因为在多个协程里分别读取数据的时候，只有一个协程里能获取某一条数据，而不是所有协程都能获取到某一条数据。
 * 每个订阅者不能获取完整的事件流。故而Channel不适合。
 * （不能保证只有一个订阅者的前提下，都不要用，直接不建议，因为无法保证）--》
 * Compose中使用的比较多。--》独立的模块做单订阅者的事，就是可行的。
 *
 * 事件流订阅：用SharedFlow
 *
 * Channel现在最主要的作用是做Flow的下层API的支撑，比如Flow的buffer功能，就是Channel缓冲到另一个协程来实现的，属于框架自身的实现原理。
 *
 * Flow的很多逻辑和Channel是相同的，API也有共通之处。
 */
@OptIn(ExperimentalCoroutinesApi::class)
fun main() = runBlocking<Unit> {
  val scope = CoroutineScope(EmptyCoroutineContext)
  val channel = Channel<List<Contributor>>()
  scope.launch {
    channel.send(gitHub.contributors("square", "retrofit"))
  }
  scope.launch {
    while (isActive) {
      channel.receive()
    }
  }
  scope.launch {
    while (isActive) {
      channel.receive()
    }
  }
  val receiver = scope.produce {
    while (isActive) {
      val data = gitHub.contributors("square", "retrofit")
      send(data)
    }
  }
  launch {
    delay(5000)
    while (isActive) {
      println("Contributors: ${receiver.receive()}")
    }
  }
  delay(10000)
}